概况
在三维地形上叠加精细化网格预报, 分析、检验和展示精细化网格预报产品.
Setup
安装rayshader程序库用于三维显示: install.packages(“devtools”) 安装nmcMetIO程序库用于读取Micaps服务器数据以及处理数据: devtools::install_github(“nmcdev/nmcMetIO”)
载入必备的程序库用于读取, 处理和可视化数据.
library(ggplot2)
library(raster)
library(png)
library(rayshader)
library(leaflet)
library(metR)
library(nmcMetIO)
准备地形数据
下载和载入地形数据
从网站"https://www.gmrt.org/services/gridserverinfo.php#!/services/getGMRTGrid"下载地形数据. 选择覆盖延庆冬奥赛区范围(115.3~116.3, 40.1~40.7), 输出为geotiff格式, 分辨率选择最大.
读入地形数据, 由于数据内可能存在NA值, rayshader无法处理, 可以用最低值来填充.
# load elevation data
elev_file <- "data/winter_olympic/yanqing_01.tif"
elev_img <- raster::raster(elev_file)
# convert it to a matrix which rayshader can handle.
elev_matrix <- raster_to_matrix(elev_img)
[1] "Dimensions of matrix are: 1822x1435"
# fill NA values
elev_matrix[!is.finite(elev_matrix)] <- min(elev_matrix, na.rm=TRUE)
显示地形数据在地图上的范围
选择地形区域为冬奥赛的延庆赛区. 从上述地形数据中获得范围的经纬度, 然后调用leaflet显示在地图上.
# define bounding box with longitude/latitude coordinates
bbox <- list(p1 = list(long = elev_img@extent@xmin, lat = elev_img@extent@ymin),
p2 = list(long = elev_img@extent@xmax, lat = elev_img@extent@ymax))
# define olympic venue
point = list(lon=115.809968, lat=40.549504)
# show the selected region for olympic region
leaflet(width = "100%") %>%
addProviderTiles(providers$Esri.WorldTopoMap) %>%
addRectangles(
lng1 = bbox$p1$long, lat1 = bbox$p1$lat, lng2 = bbox$p2$long, lat2 = bbox$p2$lat, fillColor="transparent"
) %>%
addMarkers(
lng=point$lon, lat=point$lat, label="延庆赛道"
) %>%
fitBounds(
lng1 = bbox$p1$long, lat1 = bbox$p1$lat, lng2 = bbox$p2$long, lat2 = bbox$p2$lat,
)
地形数据二维可视化
计算地形阴影层, 存入变量, 可以在三维地形显示是叠加到地形上. 将地形数据和阴影层叠加起来, 展示二维效果.
# calculate rayshader layers
ambmat <- ambient_shade(elev_matrix, zscale = 30)
raymat <- ray_shade(elev_matrix, zscale = 30, lambert = TRUE)
# plot 2D
elev_matrix %>%
sphere_shade(texture = "imhof4") %>%
add_shadow(raymat, max_darken = 0.5) %>%
add_shadow(ambmat, max_darken = 0.5) %>%
plot_map()

地图数据
下载地图数据, 用于叠加在地形上面. 调用get_arcgis_map_image从Arcgis服务器上获取区域的卫星影像图.
# get map image size
image_size <- define_image_size(bbox, major_dim = 2000)
overlay_file <- "data/winter_olympic/yanqing_02_map.png"
if(!file.exists(overlay_file)){
get_arcgis_map_image(bbox, map_type = "World_Topo_Map", file = overlay_file,
width = image_size$width, height = image_size$height,
sr_bbox=4326)
}
overlay_img <- png::readPNG(overlay_file)
plot.new()
rasterImage(overlay_img, 0,0,1,1)

显示精细化网格预报数据
最高气温数据
读入精细化网格预报的日最高温度数据, 并且存入数据文件, 以备后面使用. 由于精细化网格预报为5km, 相对于小区域分辨率比较粗, 需要插值到更高分辨率网格上, 并且进行一定程度的平滑. 最后调用metR的填充等值线函数绘制, 保存图像用于后面在三维地形上面叠加.
# load fine gridded forecast
datafile = "data/winter_olympic/fine_gridde_forecast_max_temp.rds"
if(!file.exists(datafile)){
data <- retrieve_micaps_model_grid("NWFD_SCMOC/MAXIMUM_TEMPERATURE/2M_ABOVE_GROUND/", filename="19122708.024")
saveRDS(data, file=datafile)
}
data <- readRDS(datafile)
data <- data[lon >= bbox$p1$long & lon <= bbox$p2$long & lat >= bbox$p1$lat & lat <= bbox$p2$lat]
# interpolate to olympic venue
point_values = data[, Interpolate(var1 ~ lon + lat, point$lon, point$lat, grid=FALSE)]
# smooth the data
out <- smooth2d(data$lon, data$lat, data$var1, theta=0.03, nx=128, ny=128)
# plot contour image
colors <- c("#3D0239","#FA00FC","#090079","#5E9DF8","#2E5E7F",
"#06F9FB","#0BF40B","#006103","#FAFB07","#D50404","#5A0303")
ggplot(out, aes(x, y, z = z)) +
geom_contour_fill(breaks=seq(-8, 6, by=0.5)) +
scale_fill_gradientn(name="Max\nTem", colours=colors, limits=c(-8, 6)) +
scale_x_continuous(expand=c(0,0)) +
scale_y_continuous(expand=c(0,0)) +
guides(fill=FALSE)+
theme(panel.spacing=grid::unit(0, "mm"),
plot.margin=grid::unit(rep(-1.25,4),"lines"),
axis.text=element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank())
overlay_file_temp <- "data/winter_olympic/fine_gridde_forecast_max_temp.png"
ggsave(overlay_file_temp)

风场数据
采用metR的geom_streamline函数来绘制风场的流线图, 用于叠加在三维地形上面. 这里采用GRAPES 3km模式数据, 能更好地体现地形特征.
# load fine gridded forecast
datafile = "data/winter_olympic/fine_gridde_forecast_uwind.rds"
if(!file.exists(datafile)){
dataU <- retrieve_micaps_model_grid("GRAPES_3KM/UGRD/10M_ABOVE_GROUND/", filename="19122708.030")
saveRDS(dataU, file=datafile)
}
dataU <- readRDS(datafile)
datafile = "data/winter_olympic/fine_gridde_forecast_vwind.rds"
if(!file.exists(datafile)){
dataV <- retrieve_micaps_model_grid("GRAPES_3KM/VGRD/10M_ABOVE_GROUND/", filename="19122708.030")
saveRDS(dataV, file=datafile)
}
dataV <- readRDS(datafile)
dataU <- dataU[lon >= bbox$p1$long & lon <= bbox$p2$long & lat >= bbox$p1$lat & lat <= bbox$p2$lat]
dataV <- dataV[lon >= bbox$p1$long & lon <= bbox$p2$long & lat >= bbox$p1$lat & lat <= bbox$p2$lat]
dataUV <- merge(dataU, dataV, by=c("lon", "lat", "lev", "time", "initTime", "fhour"))
#
ggplot(dataUV, aes(lon, lat)) +
geom_streamline(aes(dx=dataUV$var1.x, dy=dataUV$var1.y, size=..step..,
alpha=..step.., color=sqrt(..dx..^2 + ..dy..^2)),
L=0.12, res=2, arrow=NULL, n=20, lineend="round") +
viridis::scale_color_viridis(guide="none") +
scale_size(range=c(0,1), guide="none") +
scale_alpha(guide="none") +
scale_x_continuous(expand=c(0,0)) +
scale_y_continuous(expand=c(0,0)) +
theme(panel.spacing=grid::unit(0, "mm"),
plot.margin=grid::unit(rep(-1.25,4),"lines"),
panel.background = element_rect(fill = "transparent"), # bg of the panel
plot.background = element_rect(fill = "transparent", color = NA), # bg of the plot
axis.text=element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank())
overlay_file_wind <- "data/winter_olympic/fine_gridde_forecast_wind.png"
ggsave(overlay_file_wind)

三维地形分析
地形上叠加上述生成的地图, 气温分布以及风场流线图.
使用add_overlay将卫星影响叠加在二维地形图上.
overlay_img <- png::readPNG(overlay_file)
overlay_img_temp <- png::readPNG(overlay_file_temp)
overlay_img_wind <- png::readPNG(overlay_file_wind)
# 2D plot with map overlay
elev_matrix %>%
sphere_shade(texture = "imhof4") %>%
add_overlay(overlay_img, alphalayer = 0.95) %>%
add_overlay(overlay_img_wind, alphalayer = 0.6) %>%
add_overlay(overlay_img_temp, alphalayer = 0.6) %>%
add_shadow(raymat, max_darken = 0.4) %>%
add_shadow(ambmat, max_darken = 0.4) %>%
plot_map()

显示三维地形图.
zscale <- 20
rgl::clear3d()
elev_matrix %>%
sphere_shade(texture = "imhof4") %>%
add_overlay(overlay_img, alphalayer = 0.95) %>%
add_overlay(overlay_img_wind, alphalayer = 0.6) %>%
add_overlay(overlay_img_temp, alphalayer = 0.6) %>%
add_shadow(raymat, max_darken = 0.4) %>%
add_shadow(ambmat, max_darken = 0.4) %>%
plot_3d(elev_matrix, zscale = zscale, windowsize = c(1000, 800),
water = FALSE, soliddepth = -max(elev_matrix, na.rm=TRUE)/zscale, wateralpha = 0,
theta = 25, phi = 30, zoom = 0.65, fov = 60)
Sys.sleep(10)
render_snapshot()

label <- list(text=paste0("Olympic Track: ", as.character(round(point_values$var1,digits=2)), "C"))
label$pos <- find_image_coordinates(long=point$lon, lat=point$lat, bbox=bbox,
image_width=dim(elev_matrix)[1], image_height=dim(elev_matrix)[2])
render_label(elev_matrix, x = label$pos$x, y = label$pos$y, z = 6500,
zscale = zscale, text = label$text, textsize=2, linewidth = 5, freetype=FALSE)
render_snapshot()

LS0tDQp0aXRsZTogIuS4iee7tOWcsOW9ouWPoOWKoOe9keagvOmihOaKpeWPr+inhuWMluWIhuaekCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMjIOamguWGtQ0KICDlnKjkuInnu7TlnLDlvaLkuIrlj6DliqDnsr7nu4bljJbnvZHmoLzpooTmiqUsIOWIhuaekOOAgeajgOmqjOWSjOWxleekuueyvue7huWMlue9keagvOmihOaKpeS6p+WTgS4gDQoNCg0KIyMjIFNldHVwDQogIOWuieijhXJheXNoYWRlcueoi+W6j+W6k+eUqOS6juS4iee7tOaYvuekujogaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQ0KICDlronoo4VubWNNZXRJT+eoi+W6j+W6k+eUqOS6juivu+WPlk1pY2Fwc+acjeWKoeWZqOaVsOaNruS7peWPiuWkhOeQhuaVsOaNrjogZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJubWNkZXYvbm1jTWV0SU8iKQ0KDQogIOi9veWFpeW/heWkh+eahOeoi+W6j+W6k+eUqOS6juivu+WPliwg5aSE55CG5ZKM5Y+v6KeG5YyW5pWw5o2uLg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHJhc3RlcikNCmxpYnJhcnkocG5nKQ0KbGlicmFyeShyYXlzaGFkZXIpDQpsaWJyYXJ5KGxlYWZsZXQpDQpsaWJyYXJ5KG1ldFIpDQpsaWJyYXJ5KG5tY01ldElPKQ0KYGBgDQoNCg0KIyMg5YeG5aSH5Zyw5b2i5pWw5o2uDQoNCiMjIyDkuIvovb3lkozovb3lhaXlnLDlvaLmlbDmja4NCiAg5LuO572R56uZImh0dHBzOi8vd3d3LmdtcnQub3JnL3NlcnZpY2VzL2dyaWRzZXJ2ZXJpbmZvLnBocCMhL3NlcnZpY2VzL2dldEdNUlRHcmlkIuS4i+i9veWcsOW9ouaVsOaNri4g6YCJ5oup6KaG55uW5bu25bqG5Yas5aWl6LWb5Yy66IyD5Zu0KDExNS4zfjExNi4zLCA0MC4xfjQwLjcpLCDovpPlh7rkuLpnZW90aWZm5qC85byPLCDliIbovqjnjofpgInmi6nmnIDlpKcuDQogIA0KICDor7vlhaXlnLDlvaLmlbDmja4sIOeUseS6juaVsOaNruWGheWPr+iDveWtmOWcqE5B5YC8LCByYXlzaGFkZXLml6Dms5XlpITnkIYsIOWPr+S7peeUqOacgOS9juWAvOadpeWhq+WFhS4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGxvYWQgZWxldmF0aW9uIGRhdGENCmVsZXZfZmlsZSA8LSAiZGF0YS93aW50ZXJfb2x5bXBpYy95YW5xaW5nXzAxLnRpZiINCmVsZXZfaW1nIDwtIHJhc3Rlcjo6cmFzdGVyKGVsZXZfZmlsZSkNCg0KIyBjb252ZXJ0IGl0IHRvIGEgbWF0cml4IHdoaWNoIHJheXNoYWRlciBjYW4gaGFuZGxlLg0KZWxldl9tYXRyaXggPC0gcmFzdGVyX3RvX21hdHJpeChlbGV2X2ltZykNCg0KIyBmaWxsIE5BIHZhbHVlcw0KZWxldl9tYXRyaXhbIWlzLmZpbml0ZShlbGV2X21hdHJpeCldIDwtIG1pbihlbGV2X21hdHJpeCwgbmEucm09VFJVRSkNCmBgYA0KDQojIyMg5pi+56S65Zyw5b2i5pWw5o2u5Zyo5Zyw5Zu+5LiK55qE6IyD5Zu0DQogIOmAieaLqeWcsOW9ouWMuuWfn+S4uuWGrOWlpei1m+eahOW7tuW6hui1m+WMui4g5LuO5LiK6L+w5Zyw5b2i5pWw5o2u5Lit6I635b6X6IyD5Zu055qE57uP57qs5bqmLCDnhLblkI7osIPnlKhsZWFmbGV05pi+56S65Zyo5Zyw5Zu+5LiKLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KIyBkZWZpbmUgYm91bmRpbmcgYm94IHdpdGggbG9uZ2l0dWRlL2xhdGl0dWRlIGNvb3JkaW5hdGVzDQpiYm94IDwtIGxpc3QocDEgPSBsaXN0KGxvbmcgPSBlbGV2X2ltZ0BleHRlbnRAeG1pbiwgbGF0ID0gZWxldl9pbWdAZXh0ZW50QHltaW4pLA0KICAgICAgICAgICAgIHAyID0gbGlzdChsb25nID0gZWxldl9pbWdAZXh0ZW50QHhtYXgsIGxhdCA9IGVsZXZfaW1nQGV4dGVudEB5bWF4KSkNCg0KIyBkZWZpbmUgb2x5bXBpYyB2ZW51ZQ0KcG9pbnQgPSBsaXN0KGxvbj0xMTUuODA5OTY4LCBsYXQ9NDAuNTQ5NTA0KQ0KDQojIHNob3cgdGhlIHNlbGVjdGVkIHJlZ2lvbiBmb3Igb2x5bXBpYyByZWdpb24NCmxlYWZsZXQod2lkdGggPSAiMTAwJSIpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRFc3JpLldvcmxkVG9wb01hcCkgJT4lIA0KICBhZGRSZWN0YW5nbGVzKA0KICAgIGxuZzEgPSBiYm94JHAxJGxvbmcsIGxhdDEgPSBiYm94JHAxJGxhdCwgbG5nMiA9IGJib3gkcDIkbG9uZywgbGF0MiA9IGJib3gkcDIkbGF0LCBmaWxsQ29sb3I9InRyYW5zcGFyZW50Ig0KICApICU+JQ0KICBhZGRNYXJrZXJzKA0KICAgIGxuZz1wb2ludCRsb24sIGxhdD1wb2ludCRsYXQsIGxhYmVsPSLlu7bluobotZvpgZMiDQogICkgJT4lDQogIGZpdEJvdW5kcygNCiAgICBsbmcxID0gYmJveCRwMSRsb25nLCBsYXQxID0gYmJveCRwMSRsYXQsIGxuZzIgPSBiYm94JHAyJGxvbmcsIGxhdDIgPSBiYm94JHAyJGxhdCwNCiAgKQ0KYGBgDQoNCiMjIyDlnLDlvaLmlbDmja7kuoznu7Tlj6/op4bljJYNCiAg6K6h566X5Zyw5b2i6Zi05b2x5bGCLCDlrZjlhaXlj5jph48sIOWPr+S7peWcqOS4iee7tOWcsOW9ouaYvuekuuaYr+WPoOWKoOWIsOWcsOW9ouS4ii4g5bCG5Zyw5b2i5pWw5o2u5ZKM6Zi05b2x5bGC5Y+g5Yqg6LW35p2lLCDlsZXnpLrkuoznu7TmlYjmnpwuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBjYWxjdWxhdGUgcmF5c2hhZGVyIGxheWVycw0KYW1ibWF0IDwtIGFtYmllbnRfc2hhZGUoZWxldl9tYXRyaXgsIHpzY2FsZSA9IDMwKQ0KcmF5bWF0IDwtIHJheV9zaGFkZShlbGV2X21hdHJpeCwgenNjYWxlID0gMzAsIGxhbWJlcnQgPSBUUlVFKQ0KDQojIHBsb3QgMkQNCmVsZXZfbWF0cml4ICU+JQ0KICBzcGhlcmVfc2hhZGUodGV4dHVyZSA9ICJpbWhvZjQiKSAlPiUNCiAgYWRkX3NoYWRvdyhyYXltYXQsIG1heF9kYXJrZW4gPSAwLjUpICU+JQ0KICBhZGRfc2hhZG93KGFtYm1hdCwgbWF4X2RhcmtlbiA9IDAuNSkgJT4lDQogIHBsb3RfbWFwKCkNCmBgYA0KDQojIyDlnLDlm77mlbDmja4NCg0KICDkuIvovb3lnLDlm77mlbDmja4sIOeUqOS6juWPoOWKoOWcqOWcsOW9ouS4iumdoi4g6LCD55SoW2dldF9hcmNnaXNfbWFwX2ltYWdlXShodHRwczovL2dpdGh1Yi5jb20vbm1jZGV2L25tY01ldElPL2Jsb2IvbWFzdGVyL1IvZ2V0X21hcF9kYXRhLlIp5LuOQXJjZ2lz5pyN5Yqh5Zmo5LiK6I635Y+W5Yy65Z+f55qE5Y2r5pif5b2x5YOP5Zu+Lg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgZ2V0IG1hcCBpbWFnZSBzaXplDQppbWFnZV9zaXplIDwtIGRlZmluZV9pbWFnZV9zaXplKGJib3gsIG1ham9yX2RpbSA9IDIwMDApDQoNCm92ZXJsYXlfZmlsZSA8LSAiZGF0YS93aW50ZXJfb2x5bXBpYy95YW5xaW5nXzAyX21hcC5wbmciDQppZighZmlsZS5leGlzdHMob3ZlcmxheV9maWxlKSl7DQogIGdldF9hcmNnaXNfbWFwX2ltYWdlKGJib3gsIG1hcF90eXBlID0gIldvcmxkX1RvcG9fTWFwIiwgZmlsZSA9IG92ZXJsYXlfZmlsZSwNCiAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSBpbWFnZV9zaXplJHdpZHRoLCBoZWlnaHQgPSBpbWFnZV9zaXplJGhlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgIHNyX2Jib3g9NDMyNikNCn0NCm92ZXJsYXlfaW1nIDwtIHBuZzo6cmVhZFBORyhvdmVybGF5X2ZpbGUpDQpwbG90Lm5ldygpIA0KcmFzdGVySW1hZ2Uob3ZlcmxheV9pbWcsIDAsMCwxLDEpDQpgYGANCg0KDQoNCg0KIyMg5pi+56S657K+57uG5YyW572R5qC86aKE5oql5pWw5o2uDQoNCiMjIyDmnIDpq5jmsJTmuKnmlbDmja4NCiAg6K+75YWl57K+57uG5YyW572R5qC86aKE5oql55qE5pel5pyA6auY5rip5bqm5pWw5o2uLCDlubbkuJTlrZjlhaXmlbDmja7mlofku7YsIOS7peWkh+WQjumdouS9v+eUqC4g55Sx5LqO57K+57uG5YyW572R5qC86aKE5oql5Li6NWttLCDnm7jlr7nkuo7lsI/ljLrln5/liIbovqjnjofmr5TovoPnspcsIOmcgOimgeaPkuWAvOWIsOabtOmrmOWIhui+qOeOh+e9keagvOS4iiwg5bm25LiU6L+b6KGM5LiA5a6a56iL5bqm55qE5bmz5ruRLiDmnIDlkI7osIPnlKhtZXRS55qE5aGr5YWF562J5YC857q/5Ye95pWw57uY5Yi2LCDkv53lrZjlm77lg4/nlKjkuo7lkI7pnaLlnKjkuInnu7TlnLDlvaLkuIrpnaLlj6DliqAuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBsb2FkIGZpbmUgZ3JpZGRlZCBmb3JlY2FzdA0KZGF0YWZpbGUgPSAiZGF0YS93aW50ZXJfb2x5bXBpYy9maW5lX2dyaWRkZV9mb3JlY2FzdF9tYXhfdGVtcC5yZHMiDQppZighZmlsZS5leGlzdHMoZGF0YWZpbGUpKXsNCiAgZGF0YSA8LSByZXRyaWV2ZV9taWNhcHNfbW9kZWxfZ3JpZCgiTldGRF9TQ01PQy9NQVhJTVVNX1RFTVBFUkFUVVJFLzJNX0FCT1ZFX0dST1VORC8iLCBmaWxlbmFtZT0iMTkxMjI3MDguMDI0IikNCiAgc2F2ZVJEUyhkYXRhLCBmaWxlPWRhdGFmaWxlKQ0KfQ0KZGF0YSA8LSByZWFkUkRTKGRhdGFmaWxlKQ0KZGF0YSA8LSBkYXRhW2xvbiA+PSBiYm94JHAxJGxvbmcgJiBsb24gPD0gYmJveCRwMiRsb25nICYgbGF0ID49IGJib3gkcDEkbGF0ICYgbGF0IDw9IGJib3gkcDIkbGF0XQ0KDQojIGludGVycG9sYXRlIHRvIG9seW1waWMgdmVudWUNCnBvaW50X3ZhbHVlcyA9IGRhdGFbLCBJbnRlcnBvbGF0ZSh2YXIxIH4gbG9uICsgbGF0LCBwb2ludCRsb24sIHBvaW50JGxhdCwgZ3JpZD1GQUxTRSldDQoNCiMgc21vb3RoIHRoZSBkYXRhDQpvdXQgPC0gc21vb3RoMmQoZGF0YSRsb24sIGRhdGEkbGF0LCBkYXRhJHZhcjEsIHRoZXRhPTAuMDMsIG54PTEyOCwgbnk9MTI4KQ0KDQojIHBsb3QgY29udG91ciBpbWFnZQ0KY29sb3JzIDwtIGMoIiMzRDAyMzkiLCIjRkEwMEZDIiwiIzA5MDA3OSIsIiM1RTlERjgiLCIjMkU1RTdGIiwNCiAgICAgICAgICAgICIjMDZGOUZCIiwiIzBCRjQwQiIsIiMwMDYxMDMiLCIjRkFGQjA3IiwiI0Q1MDQwNCIsIiM1QTAzMDMiKQ0KZ2dwbG90KG91dCwgYWVzKHgsIHksIHogPSB6KSkgKw0KICBnZW9tX2NvbnRvdXJfZmlsbChicmVha3M9c2VxKC04LCA2LCBieT0wLjUpKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKG5hbWU9Ik1heFxuVGVtIiwgY29sb3Vycz1jb2xvcnMsIGxpbWl0cz1jKC04LCA2KSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kPWMoMCwwKSkgKw0KICBndWlkZXMoZmlsbD1GQUxTRSkrDQogIHRoZW1lKHBhbmVsLnNwYWNpbmc9Z3JpZDo6dW5pdCgwLCAibW0iKSwNCiAgICAgICAgcGxvdC5tYXJnaW49Z3JpZDo6dW5pdChyZXAoLTEuMjUsNCksImxpbmVzIiksDQogICAgICAgIGF4aXMudGV4dD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkNCg0Kb3ZlcmxheV9maWxlX3RlbXAgPC0gImRhdGEvd2ludGVyX29seW1waWMvZmluZV9ncmlkZGVfZm9yZWNhc3RfbWF4X3RlbXAucG5nIg0KZ2dzYXZlKG92ZXJsYXlfZmlsZV90ZW1wKQ0KYGBgDQoNCiMjIyDpo47lnLrmlbDmja4NCiAg6YeH55SobWV0UueahGdlb21fc3RyZWFtbGluZeWHveaVsOadpee7mOWItumjjuWcuueahOa1gee6v+Wbviwg55So5LqO5Y+g5Yqg5Zyo5LiJ57u05Zyw5b2i5LiK6Z2iLiDov5nph4zph4fnlKhHUkFQRVMgM2tt5qih5byP5pWw5o2uLCDog73mm7Tlpb3lnLDkvZPnjrDlnLDlvaLnibnlvoEuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBsb2FkIGZpbmUgZ3JpZGRlZCBmb3JlY2FzdA0KZGF0YWZpbGUgPSAiZGF0YS93aW50ZXJfb2x5bXBpYy9maW5lX2dyaWRkZV9mb3JlY2FzdF91d2luZC5yZHMiDQppZighZmlsZS5leGlzdHMoZGF0YWZpbGUpKXsNCiAgZGF0YVUgPC0gcmV0cmlldmVfbWljYXBzX21vZGVsX2dyaWQoIkdSQVBFU18zS00vVUdSRC8xME1fQUJPVkVfR1JPVU5ELyIsIGZpbGVuYW1lPSIxOTEyMjcwOC4wMzAiKQ0KICBzYXZlUkRTKGRhdGFVLCBmaWxlPWRhdGFmaWxlKQ0KfQ0KZGF0YVUgPC0gcmVhZFJEUyhkYXRhZmlsZSkNCmRhdGFmaWxlID0gImRhdGEvd2ludGVyX29seW1waWMvZmluZV9ncmlkZGVfZm9yZWNhc3RfdndpbmQucmRzIg0KaWYoIWZpbGUuZXhpc3RzKGRhdGFmaWxlKSl7DQogIGRhdGFWIDwtIHJldHJpZXZlX21pY2Fwc19tb2RlbF9ncmlkKCJHUkFQRVNfM0tNL1ZHUkQvMTBNX0FCT1ZFX0dST1VORC8iLCBmaWxlbmFtZT0iMTkxMjI3MDguMDMwIikNCiAgc2F2ZVJEUyhkYXRhViwgZmlsZT1kYXRhZmlsZSkNCn0NCmRhdGFWIDwtIHJlYWRSRFMoZGF0YWZpbGUpDQpkYXRhVSA8LSBkYXRhVVtsb24gPj0gYmJveCRwMSRsb25nICYgbG9uIDw9IGJib3gkcDIkbG9uZyAmIGxhdCA+PSBiYm94JHAxJGxhdCAmIGxhdCA8PSBiYm94JHAyJGxhdF0NCmRhdGFWIDwtIGRhdGFWW2xvbiA+PSBiYm94JHAxJGxvbmcgJiBsb24gPD0gYmJveCRwMiRsb25nICYgbGF0ID49IGJib3gkcDEkbGF0ICYgbGF0IDw9IGJib3gkcDIkbGF0XQ0KZGF0YVVWIDwtIG1lcmdlKGRhdGFVLCBkYXRhViwgYnk9YygibG9uIiwgImxhdCIsICJsZXYiLCAidGltZSIsICJpbml0VGltZSIsICJmaG91ciIpKQ0KDQojIA0KZ2dwbG90KGRhdGFVViwgYWVzKGxvbiwgbGF0KSkgKw0KICBnZW9tX3N0cmVhbWxpbmUoYWVzKGR4PWRhdGFVViR2YXIxLngsIGR5PWRhdGFVViR2YXIxLnksIHNpemU9Li5zdGVwLi4sIA0KICAgICAgICAgICAgICAgICAgICAgIGFscGhhPS4uc3RlcC4uLCBjb2xvcj1zcXJ0KC4uZHguLl4yICsgLi5keS4uXjIpKSwNCiAgICAgICAgICAgICAgICAgIEw9MC4xMiwgcmVzPTIsIGFycm93PU5VTEwsIG49MjAsIGxpbmVlbmQ9InJvdW5kIikgKyANCiAgdmlyaWRpczo6c2NhbGVfY29sb3JfdmlyaWRpcyhndWlkZT0ibm9uZSIpICsNCiAgc2NhbGVfc2l6ZShyYW5nZT1jKDAsMSksIGd1aWRlPSJub25lIikgKw0KICBzY2FsZV9hbHBoYShndWlkZT0ibm9uZSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAsMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZD1jKDAsMCkpICsNCiAgdGhlbWUocGFuZWwuc3BhY2luZz1ncmlkOjp1bml0KDAsICJtbSIpLA0KICAgICAgICBwbG90Lm1hcmdpbj1ncmlkOjp1bml0KHJlcCgtMS4yNSw0KSwibGluZXMiKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiksICMgYmcgb2YgdGhlIHBhbmVsDQogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3IgPSBOQSksICMgYmcgb2YgdGhlIHBsb3QNCiAgICAgICAgYXhpcy50ZXh0PWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF9ibGFuaygpKQ0KDQpvdmVybGF5X2ZpbGVfd2luZCA8LSAiZGF0YS93aW50ZXJfb2x5bXBpYy9maW5lX2dyaWRkZV9mb3JlY2FzdF93aW5kLnBuZyINCmdnc2F2ZShvdmVybGF5X2ZpbGVfd2luZCkNCmBgYA0KDQojIyDkuInnu7TlnLDlvaLliIbmnpANCg0KIyMg5Zyw5b2i5LiK5Y+g5Yqg5LiK6L+w55Sf5oiQ55qE5Zyw5Zu+LCDmsJTmuKnliIbluIPku6Xlj4rpo47lnLrmtYHnur/lm74uDQoNCiAg5L2/55SoYWRkX292ZXJsYXnlsIbljavmmJ/lvbHlk43lj6DliqDlnKjkuoznu7TlnLDlvaLlm77kuIouDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kb3ZlcmxheV9pbWcgPC0gcG5nOjpyZWFkUE5HKG92ZXJsYXlfZmlsZSkNCm92ZXJsYXlfaW1nX3RlbXAgPC0gcG5nOjpyZWFkUE5HKG92ZXJsYXlfZmlsZV90ZW1wKQ0Kb3ZlcmxheV9pbWdfd2luZCA8LSBwbmc6OnJlYWRQTkcob3ZlcmxheV9maWxlX3dpbmQpDQoNCiMgMkQgcGxvdCB3aXRoIG1hcCBvdmVybGF5DQplbGV2X21hdHJpeCAlPiUNCiAgc3BoZXJlX3NoYWRlKHRleHR1cmUgPSAiaW1ob2Y0IikgJT4lDQogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nLCBhbHBoYWxheWVyID0gMC45NSkgJT4lDQogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nX3dpbmQsIGFscGhhbGF5ZXIgPSAwLjYpICU+JQ0KICBhZGRfb3ZlcmxheShvdmVybGF5X2ltZ190ZW1wLCBhbHBoYWxheWVyID0gMC42KSAlPiUNCiAgYWRkX3NoYWRvdyhyYXltYXQsIG1heF9kYXJrZW4gPSAwLjQpICU+JQ0KICBhZGRfc2hhZG93KGFtYm1hdCwgbWF4X2RhcmtlbiA9IDAuNCkgJT4lDQogIHBsb3RfbWFwKCkNCmBgYA0KDQogIOaYvuekuuS4iee7tOWcsOW9ouWbvi4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xNX0NCnpzY2FsZSA8LSAyMA0KDQpyZ2w6OmNsZWFyM2QoKQ0KZWxldl9tYXRyaXggJT4lIA0KICBzcGhlcmVfc2hhZGUodGV4dHVyZSA9ICJpbWhvZjQiKSAlPiUgDQogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nLCBhbHBoYWxheWVyID0gMC45NSkgJT4lDQogIGFkZF9vdmVybGF5KG92ZXJsYXlfaW1nX3dpbmQsIGFscGhhbGF5ZXIgPSAwLjYpICU+JQ0KICBhZGRfb3ZlcmxheShvdmVybGF5X2ltZ190ZW1wLCBhbHBoYWxheWVyID0gMC42KSAlPiUNCiAgYWRkX3NoYWRvdyhyYXltYXQsIG1heF9kYXJrZW4gPSAwLjQpICU+JQ0KICBhZGRfc2hhZG93KGFtYm1hdCwgbWF4X2RhcmtlbiA9IDAuNCkgJT4lDQogIHBsb3RfM2QoZWxldl9tYXRyaXgsIHpzY2FsZSA9IHpzY2FsZSwgd2luZG93c2l6ZSA9IGMoMTAwMCwgODAwKSwNCiAgICAgICAgICB3YXRlciA9IEZBTFNFLCBzb2xpZGRlcHRoID0gLW1heChlbGV2X21hdHJpeCwgbmEucm09VFJVRSkvenNjYWxlLCB3YXRlcmFscGhhID0gMCwNCiAgICAgICAgICB0aGV0YSA9IDI1LCBwaGkgPSAzMCwgem9vbSA9IDAuNjUsIGZvdiA9IDYwKQ0KU3lzLnNsZWVwKDEwKQ0KcmVuZGVyX3NuYXBzaG90KCkNCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xNX0NCmxhYmVsIDwtIGxpc3QodGV4dD1wYXN0ZTAoIk9seW1waWMgVHJhY2s6ICIsIGFzLmNoYXJhY3Rlcihyb3VuZChwb2ludF92YWx1ZXMkdmFyMSxkaWdpdHM9MikpLCAiQyIpKQ0KbGFiZWwkcG9zIDwtIGZpbmRfaW1hZ2VfY29vcmRpbmF0ZXMobG9uZz1wb2ludCRsb24sIGxhdD1wb2ludCRsYXQsIGJib3g9YmJveCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbWFnZV93aWR0aD1kaW0oZWxldl9tYXRyaXgpWzFdLCBpbWFnZV9oZWlnaHQ9ZGltKGVsZXZfbWF0cml4KVsyXSkNCnJlbmRlcl9sYWJlbChlbGV2X21hdHJpeCwgeCA9IGxhYmVsJHBvcyR4LCB5ID0gbGFiZWwkcG9zJHksIHogPSA2NTAwLA0KICAgICAgICAgICAgIHpzY2FsZSA9IHpzY2FsZSwgdGV4dCA9IGxhYmVsJHRleHQsIHRleHRzaXplPTIsIGxpbmV3aWR0aCA9IDUsIGZyZWV0eXBlPUZBTFNFKQ0KDQpyZW5kZXJfc25hcHNob3QoKQ0KYGBgDQoNCg0K